<?php
###########################################################################
#  Copyright (C) 2006-2007 Glyphix, Inc. <briz@glyphix.com>               #
#                                                                         #
#  Permission is hereby granted, free of charge, to any person obtaining  #
#  a copy of this software and associated documentation files (the        #
#  "Software"), to deal in the Software without restriction, including    #
#  without limitation the rights to use, copy, modify, merge, publish,    #
#  distribute, sublicense, and/or sell copies of the Software, and to     #
#  permit persons to whom the Software is furnished to do so, subject to  #
#  the following conditions:                                              #
#                                                                         #
#  The above copyright notice and this permission notice shall be         #
#  included in all copies or substantial portions of the Software.        #
#                                                                         #
#  Except as contained in this notice, the name(s) of the above           #
#  copyright holders shall not be used in advertising or otherwise to     #
#  promote the sale, use or other dealings in this Software without       #
#  prior written authorization.                                           #
#                                                                         #
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        #
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     #
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. #
#  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR      #
#  OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,  #
#  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR  #
#  OTHER DEALINGS IN THE SOFTWARE.                                        #
###########################################################################

/**
 * GXGoogleAuth
 *
 * Authenticates against a Google account.
 * @author		Brad Brizendine <briz@glyphix.com>
 * @link		http://www.glyphix.com/
 * @license		http://opensource.org/licenses/bsd-license.php BSD License
 * @version		1.0
 * @package		GXPage
 * @subpackage	Modules
 * @copyright	Copyright 2006 - 2007, Glyphix, Inc.
 * @uses		GXModule, curl
 */

class GXGoogleAuth extends GXModule {

	// curl user agent string
	private $userAgent = 'Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.4) Gecko/20030624 Netscape/7.1 (ax)';

	// google auth params
	private $domain;		// the domain all provided email addresses must be part of
	private $url = 'https://www.google.com/accounts/ClientLogin';			// the google auth url (ClientLogin)
	protected $service;		// the google service we want a token for
	protected $source;		// the identifier we want google to use for its own logging purposes
	private $type = 'HOSTED_OR_GOOGLE';			// the type of google auth to use (GOOGLE, HOSTED or HOSTED_OR_GOOGLE)
	protected $timeout = 15;	// the number of minutes the session will time out without any activity
	private $token;			// the token provided by google

	// the member's cookie name ... defaults to 'gid'
	private $session = 'gid';
	// cookie to track google auth token
	private $authCookieName = 'gtoken';
	// this member's id ... the user's email address
	private $uid;

	// init GXModule and process conf
	public function __construct( $config = null ){
		parent::__construct();
		$this->GXCookie = GXCookie::getInstance();

		// either domain or username+password may be present
		$this->domain	= $this->getval($config,'domain');
		$this->username	= $this->getval($config,'username');
		$this->password	= $this->getval($config,'password');
		$this->service	= $this->getval($config,'service');
		if( !$this->service ){
			$this->service = 'xapi';
		}
		$this->timeout	= $this->getval($config,'timeout');
		if( !$this->timeout ){
			$this->timeout = 15;
		}
		$this->session	= $this->getval($config,'auth');
		// if we have no session setting in the config, use a cookie
		if( !$this->session ){
			$this->session = 'gid';
		}

		// if we have no domain, create it from the provided username
		if( !$this->domain ){
			$parts = split('@',$this->username);
			if( !$parts[1] ){
				trigger_error( 'No domain in the config, and no username to derive it', E_USER_ERROR );
				return false;
			}
			$this->domain = $parts[1];
		}

		// set the source after we determine the domain
		$this->source	= $this->getval($config,'source');
		if( !$this->source ){
			$this->source = $this->domain;
		}

		// if no session, check the cookie
		if( !($this->uid = $this->getUserID()) ){
			trigger_error( 'User is not authenticated', E_USER_WARNING );
			$this->GXCookie->delete( $this->session );
			unset($this->uid);
		}else{
			if( strtolower($this->session) != 'none' ){
				// freshen session
				$this->setSession($this->uid);
			}
		}

		return true;
	}

	/**
	 * getval
	 *
	 * Centralizes a simple function to return a value for a node,
	 * while avoiding PHP E_STRICT errors.
	 * @param object $config the GXDOM config for this module
	 * @param string $xpath what to look for
	 * @return string or null
	 */
	protected function getval( $config = null, $xpath = null ){
		$obj = $config->exec->xpath($xpath,0);
		return ($obj ? $obj->nodeValue : null);
	}

	/**
	 * getUserID
	 * Pulls the admin username from the cookie.
	 * If the user is not logged, the ID is pulled from GXPage_Params.
	 * @return string username or false on failure
	 */
	public function getUserID(){
		trigger_error('GXGoogleAuth: getUserID: ', E_USER_WARNING);
		if( !isset($this->uid) || !$this->uid ){
			$this->uid = $this->GXCookie->get($this->session);
			trigger_error('GXGoogleAuth: getUserID: ' .$this->uid, E_USER_WARNING);
			//$this->uid = $this->GXPage->Params->{$this->session};
		}
		return $this->uid;
	}

	/*
	 * showUser
	 * Builds XML for the current user.
	 * @return object GXDOM
	 */
	public function showUser(){
		if( !$this->uid ){
			trigger_error( 'Administrator is not logged in', E_USER_ERROR );
			return false;
		}
		$user = new GXDOM('User');
		$user->FirstName( $this->uid );
		$user->LastName();
		$auth = $user->Auth();
		$auth->Role(null,array('ID'=>'MEMBER'));
		$auth->Role(null,array('ID'=>'ADMIN'));
		return $user;
	}

	/**
	 * login
	 *
	 * Authenticates a user by calling Google's ClientLogin API.
	 * @param string $username the user's full email address
	 * @param string $password the user's password
	 * @return string the Auth token or false on failure
	 */
	public function login( $username = null, $password = null ){
		// config can set a hard-coded username / password ... use it if we have it
		if( !$username ){
			$username = $this->username;
		}
		if( !$password ){
			$password = $this->password;
		}
		if( !$username || !$password ){
			trigger_error( 'Missing username or password', E_USER_ERROR );
			return false;
		}
		// username must be a valid email address
		if( !preg_match('/^[a-z0-9\._-]+' .'@' .'([a-z0-9][a-z0-9-]*[a-z0-9]\.)+' .'([a-z]+\.)?' .'([a-z]+)$/',$username) ){
			trigger_error( 'That email address does not appear to be valid (' .$username .'). Use your entire Google Account email address.', E_USER_ERROR );
			return false;
		}
		// username must be @domain
		if( strpos($username,$this->domain) < 1 ){
			trigger_error( 'That email address is not part of an authorized domain.', E_USER_ERROR );
			return false;
		}

		// clean the username
		$username = trim($username);
		// set up the post args
		$args = array(
			'accountType'	=> $this->type,
			'Email'			=> $username,
			'Passwd'		=> $password,
			'service'		=> $this->service,
			'source'		=> $this->source
			);

		trigger_error('--------- GXGoogleAuth: about to call curl', E_USER_NOTICE);

		// load the GXCurl object
		$curl = new GXCurl();
		$r = $curl->fetch( $this->url, $args, GXCurl::POST );

		trigger_error('--------- GXGoogleAuth: just called curl with response [' .print_r($r, true) .']', E_USER_NOTICE);

		// parse the response
		$pairs = array();
        $lines = preg_split('/(?:\r\n|\n|\r)/', $r);
		$matches = array();
        foreach( $lines as $line ){
            if( preg_match('/^\s*([^\s=]+)\s*=\s*(.+)$/', $line, $matches) ){
                $pairs[ strtolower($matches[1]) ] = trim($matches[2]);
            }
        }

        trigger_error('---------- GXGoogleAuth: parsed curl response [' .print_r($pairs, true) .']', E_USER_NOTICE);

		// if we don't have a 200 response, it's an error
		if( $curl->getCode() != '200' ){
			// spit out user-friendly errors
			$msg = null;
			switch( $pairs['error'] ){
				case 'BadAuthentication':
					$msg = 'The login request used a username or password that is not recognized.';
					break;
				case 'NotVerified':
					$msg = 'The account email address has not been verified. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.';
					break;
				case 'TermsNotAgreed':
					$msg = 'The user has not agreed to terms. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.';
					break;
				case 'CaptchaRequired':
					$msg = 'A CAPTCHA is required. remindermed.com does not handle this yet.';
					break;
				case 'Unknown':
					$msg = 'The error is unknown or unspecified; the request contained invalid input or was malformed.';
					break;
				case 'AccountDeleted':
					$msg = 'The user account has been deleted.';
					break;
				case 'AccountDisabled':
					$msg = 'The user account has been disabled.';
					break;
				case 'ServiceDisabled':
					$msg = 'The user\'s access to the specified service has been disabled. (The user account may still be valid.)';
					break;
				case 'ServiceUnavailable':
					$msg = 'The service is not available; try again later.';
					break;
				default:
					$msg = 'Some weirdo strange error occurred: ' .print_r($pairs,true);
			}
			trigger_error( $msg, E_USER_NOTICE );
			return false;
		}

		// set the user's info
		$this->uid = $username;
		$this->setSession($username, $pairs['auth']);
		trigger_error('------ GXGoogleAuth: successfully logged in as ' .$username, E_USER_NOTICE);
		return $username;
	}

	/**
	 * logout
	 *
	 * Logout function. Duh.
	 * @return boolean
	 */
	public function logout(){
		$this->GXCookie->delete( $this->session );
		$this->GXCookie->delete( $this->authCookieName );
		unset($this->session);
		unset($this->uid);
		trigger_error( 'You have successfully signed out.', E_USER_NOTICE );
		return true;
	}

	/**
	 * setSession
	 *
	 * Sets the user's session cookie.
	 * Call each time the page is accessed in order to refresh the user's session.
	 * @return boolean
	 */
	public function setSession( $uid = null, $auth = null ){
		if( !$uid ){
			trigger_error( 'Missing uid', E_USER_ERROR );
			return false;
		}
		// store values in this class
		$this->uid = $uid;
		$this->token = $auth;
		// calc timeout ... convert to seconds
		$timeout = ($this->timeout) ? $this->timeout * 60 : null;
		$this->GXCookie->set( $this->session, $uid, $timeout );
		// set a cookie to track the auth token
		$token = ($auth) ? $auth : $this->GXCookie->get($this->authCookieName);
		$this->GXCookie->set( $this->authCookieName, $token, $timeout );
		return true;
	}

	/**
	 * getToken
	 *
	 * Returns the Google-supplied authentication token.
	 * @return string
	 */
	public function getToken(){
		return $this->token;
	}

}

?>
